-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] feat(rest): allow bypassing http response writing and custom content type #1753
Conversation
7af3258
to
d986a61
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🚀
df2a5fa
to
21c4f41
Compare
@virkt25 I refined the PR to check if a controller method returns the |
21c4f41
to
b4808b0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the new bypass much better!! Should we add a section in the README showing how to just set the content type and let the writer write the response
docs/site/Controllers.md
Outdated
@@ -214,6 +214,41 @@ export class HelloController { | |||
- `@param.query.number` specifies in the spec being generated that the route | |||
takes a parameter via query which will be a number. | |||
|
|||
## Custom Response Writing | |||
|
|||
By default, LoopBack serialize the return value from the controller methods into |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LoopBack will serialize
OR
LoopBack serializes the ...
response.end(); | ||
return; | ||
} | ||
|
||
if (result instanceof Readable || typeof result.pipe === 'function') { | ||
response.setHeader('Content-Type', 'application/octet-stream'); | ||
function setContentType(defaultType: string = 'application/json') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the variable name should be changed to type
as it can be overridden.
…type We discover an issue to return html from a controller: See loopbackio/loopback4-example-shopping#16
b4808b0
to
deb2f05
Compare
I am rather unhappy about the design changes proposed in this pull request. So far, the framework was offering pretty strong guarantees to LoopBack users: every HTTP response was produced either by The solution proposed by this pull request, where the controller sends the response directly via Express I understand we need a quick workaround to enable our shopping-cart example to serve a home page (loopbackio/loopback4-example-shopping#16) and I am fine to add an undocumented fix as a short-term workaround. I would personally use the following check to determine whether the response have been already produced:
What's wrong with letting the controller method return For a longer term solution that will be documented, I'd like us to use the approach described in #436, #436 (comment) and #788 (comment), where the controller method can return an object describing the response to be produced, e.g. To move this pull request forward, I am asking for the following changes:
|
export type Send = ( | ||
response: Response, | ||
result: OperationRetval, | ||
contentType?: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the purpose of this new parameter. I don't see it used anywhere in production code, only in unit tests.
send
is typically invoked from a Sequence. There is usually the same sequence of actions implemented for all endpoints. IMO, the decision which content-type to send back should be made in individual controller methods (or routes), not in the Sequence.
I am proposing to revert this change.
*/ | ||
export function writeResultToResponse( | ||
// not needed and responsibility should be in the sequence.send | ||
response: Response, | ||
// result returned back from invoking controller method | ||
result: OperationRetval, | ||
// content type for the response | ||
contentType?: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly here, let's revert this change please.
if (result instanceof Readable || typeof result.pipe === 'function') { | ||
response.setHeader('Content-Type', 'application/octet-stream'); | ||
function setContentType(defaultType: string = 'application/json') { | ||
if (response.getHeader('Content-Type') == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I prefer to revert this change.
Ideally, there should be only two ways how a content-type is determined:
-
The controller method/route returns data to be serialized to the response. The content-type is determined by the framework depending on the type of data (object vs. string vs. stream or buffer), request headers like
Accepts
and endpoint OpenAPI spec. Right now, we have a very limited version of this algorithm implemented in the currentwriteResultToResponse
code (objects are sent as JSON, streams as binary, strings as text). -
The controller wants to set an explicit content type, status code or change any other aspect of the HTTP response. It returns an object describing response properties (status code, headers, body) and the
send
action applies these instructions to theresponse
object. This will be enabled by Configurable response types/formats #436 in the future.
The change proposed here adds a third mode that makes it difficult to reason about the HTTP responses when reading code, because different aspects of the response (content type vs. actual response body) are produced by different pieces of code.
- The controller (or a sequence action in general) explicitly sets the content type, but it still expects the framework to convert the value returned by the controller method (or route handler) into HTTP response body.
There is of course also the fourth mode being added here, I think it should be the only scope of this pull request. It goes against the two modes I described above, but I ok with it as a temporary workaround:
- The controller (or a sequence action in general) takes full control of the response sent: it sets the status code & headers and writes the response body. The
send
action becomes a no-op.
|
||
if ( | ||
result instanceof Readable || | ||
typeof (result && result.pipe) === 'function' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://loopback.io/doc/en/contrib/style-guide.html#indentation-of-multi-line-expressions-in-if
const isStream = result instanceof Readable ||
typeof (result && result.pipe) === 'function';
if (isStream) {
// ...
}
@bajtos Thank you for the feedback. A few clarifications:
I see use cases for both modes and would love to allow both flavors. We can treat the The issues above need to be addressed in the long run. To enable our shopping example, I'll create a different (much simpler) PR to allow:
|
Isn't it better to allow the controller to just set the response type / response header? ... How much work is it for us to implement pulling the controller metadata to see what the response header should be and set it based on that? |
Close it to favor #1760 |
We discover an issue to return html from a controller:
See loopbackio/loopback4-example-shopping#16
Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated